package com.xian.im.modules.security;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.xian.im.common.constant.CommandChannelConstant;
import com.xian.im.common.enums.SourceChannelEnums;
import com.xian.im.common.model.dto.OrganizationDto;
import com.xian.im.common.model.dto.UserDto;
import com.xian.im.common.properties.SecurityProperties;
import com.xian.im.common.utils.sys.ThreadLocalUtil;
import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {

    private final SecurityProperties properties;
    public static final String USER_ACCOUNT_KEY = "uak";
    public static final String USER_REDIS_KEY = "urk";
    public static final String ORG_ID_KEY = "t";
    public static final String USER_ID_KEY = "u";
    // 0 观众端登录  1 租户登录
    public static final String LOGIN_TYPE_KEY = "ty";
    public static final String ORG_AK_KEY = "ak";
    public static final String ORG_SK_KEY = "sk";
    public static final String  LOGIN_CHANNEL_TYPE_KEY = "lctk";
    public static final String USER_CLAIMS ="claims";
    /**
     * Request Headers ： Authorization
     */
    public static final String TOKEN_HEAD_NAME = "Authorization";
    /**
     * 令牌前缀，最后留个空格 Bearer
     */
    public static final String TOKEN_START_WITH = "";
    /**
     * b端令牌过期时间 单位:秒 24小时
     */
    public static final long tokenValidityInSeconds = 24 * 3600;
    /**
     * c端令牌过期时间 单位:秒 30天
     */
    public static final long cTokenValidityInSeconds = 30 * 24 * 3600;
    /**
     * b端续期时间 1小时 单位:秒
     */
    public static final long RENEW = 3600;
    /**
     * c端续期时间 7天 单位:秒
     */
    public static final long C_RENEW = 7 * 24 * 3600;
    /**
     * token 续期检查 4小时 单位:秒
     */
    private static final long DETECT = 4 * 3600;
    /**
     * 登录时间（默认：10分钟）
     */
    public static final long LOGIN_TIME = 600;
    public Key key;
    public JwtParser jwtParser;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void afterPropertiesSet() {
        this.jwtParser = Jwts.parser();
    }

    /**
     *  创建token
     * @param user
     * @return
     */
    public String createUserToken(UserDto user) {
        BaseClaims baseClaims = BaseClaims.builder()
                .channel(user.getAccountDto().getChannel())
                .userAccount(user.getAccount())
                .userId(user.getId())
                .orgId(user.getOrgId())
                .type(0)
                .build();
        String token = Jwts.builder()
                .claim(LOGIN_TYPE_KEY,baseClaims.getType())
                .claim(ORG_ID_KEY,user.getOrgId())
                .claim(USER_ID_KEY, user.getId())
                .claim(LOGIN_CHANNEL_TYPE_KEY, user.getAccountDto().getChannel())
                .claim(USER_ACCOUNT_KEY, user.getAccount())
                .claim(ORG_ID_KEY,user.getOrgId())
                .signWith(SignatureAlgorithm.HS512, key)
                .compact();
        redisTemplate.opsForValue().set(baseClaims.getUserRedisKey(),token,properties.getDetect(),TimeUnit.SECONDS);
        return token;
    }

    /**
     *  制定某个用户token失效
     * @return
     */
    public Boolean invalid(UserDto user){
        //CommandChannelConstant.CHANNEL_ANDROID
        BaseClaims baseClaims = BaseClaims.builder()
                .channel(CommandChannelConstant.CHANNEL_ANDROID)
                .userAccount(user.getAccount())
                .userId(user.getId())
                .orgId(user.getOrgId())
                .type(0)
                .build();
        redisTemplate.delete(baseClaims.getUserRedisKey());
        return Boolean.TRUE;
    }

    /**
     *  制定某个用户token失效
     * @return
     */
    public Boolean checkUserOnline(UserDto user){
        BaseClaims baseClaims = BaseClaims.builder()
                .channel(user.getAccountDto().getChannel())
                .userAccount(user.getAccount())
                .userId(user.getId())
                .orgId(user.getOrgId())
                .type(0)
                .build();
        return redisTemplate.hasKey(baseClaims.getUserRedisKey());
    }

    /**
     *  制定某个用户token失效
     * @return
     */
    public Boolean checkOrgOnline(OrganizationDto user){
        for (SourceChannelEnums value : SourceChannelEnums.values()) {
            BaseClaims baseClaims = BaseClaims.builder()
                    .ak(user.getAccessKey())
                    .sk(user.getSecretKey())
                    .orgId(user.getId())
                    .type(1)
                    .build();
            Boolean aBoolean = redisTemplate.hasKey(baseClaims.getOrgRedisKey());
            if(aBoolean){
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    /**
     *  token失效
     * @return
     */
    public Boolean invalid(){
        BaseClaims baseClaims =  ThreadLocalUtil.get(TokenProvider.USER_CLAIMS);
        redisTemplate.delete(baseClaims.getUserRedisKey());
        return Boolean.TRUE;
    }
    /**
     *  创建token
     * @param user
     * @return
     */
    public String createOrgToken(OrganizationDto user) {
        BaseClaims baseClaims = BaseClaims.builder()
                .ak(user.getAccessKey())
                .sk(user.getSecretKey())
                .orgId(user.getId())
                .type(1)
                .build();
        String token = Jwts.builder()
                .claim(LOGIN_TYPE_KEY,baseClaims.getType())
                .claim(ORG_ID_KEY,user.getId())
                .claim(ORG_AK_KEY,user.getAccessKey())
                .claim(ORG_SK_KEY,user.getSecretKey())
                .signWith(SignatureAlgorithm.HS512, key)
                .compact();
        redisTemplate.opsForValue().set(baseClaims.getOrgRedisKey(),token,properties.getCTokenValidityInSeconds(),TimeUnit.SECONDS);
        return token;
    }

    /**
     *
     * @param baseClaims
     * @param renewTime
     */
    public void checkRenewal(BaseClaims baseClaims, Long renewTime) {
        // 判断是否续期token,计算token的过期时间
        long time = redisTemplate.getExpire(baseClaims.getRedisKey());
        Date expireDate = DateUtil.offset(new Date(), DateField.SECOND, (int) time);
        // 判断当前时间与过期时间的时间差
        long differ = (expireDate.getTime() - System.currentTimeMillis()) / 1000;
        // 如果在续期检查的范围内，则续期
        if (differ <= DETECT) {
            long renew = time + renewTime;
            if(baseClaims.getType() == 0){
                redisTemplate.expire(baseClaims.getUserRedisKey(), renew, TimeUnit.SECONDS);
            }else {
                redisTemplate.expire(baseClaims.getOrgRedisKey(), properties.getCTokenValidityInSeconds(), TimeUnit.SECONDS);
            }

        }
    }

    /**
     * http 请求获取token
     * @param request
     * @return
     */
    public String getToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(TOKEN_HEAD_NAME);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_START_WITH)) {
            // 去掉令牌前缀
            return bearerToken.replace(TOKEN_START_WITH, "").trim();
        }
        return null;
    }

    /**
     * 解析token
     * @param token
     * @return
     */
    public BaseClaims resolveBaseClaims(final String token) {
        BaseClaims baseClaims = ThreadLocalUtil.get(USER_CLAIMS);
        if (baseClaims != null) {
            return baseClaims;
        }
        Claims claims = jwtParser
                .parseClaimsJws(token)
                .getBody();
        Object userId = claims.get(USER_ID_KEY);
        Object channel = claims.get(LOGIN_CHANNEL_TYPE_KEY);
        Object userAccount = claims.get(USER_ACCOUNT_KEY);
        Object orgId = claims.get(ORG_ID_KEY);
        Object type = claims.get(LOGIN_TYPE_KEY);
        Object ak = claims.get(ORG_AK_KEY);
        Object sk = claims.get(ORG_SK_KEY);

        baseClaims = BaseClaims.builder()
                .userId(null == userId ? null :Long.valueOf(userId.toString()))
                .userAccount(null ==userAccount ?null:userAccount.toString())
                .channel(null == channel? null:String.valueOf(channel))
                .orgId(null == orgId?null :Long.valueOf(orgId.toString()))
                .sk(null == sk? null : sk.toString())
                .ak(null == ak? null : ak.toString())
                .type(null == type? null : Integer.valueOf(type.toString()))
                .build();
        ThreadLocalUtil.set(TokenProvider.USER_CLAIMS, baseClaims);
        return baseClaims;
    }
}
